package com.i72.freeway;

import com.alibaba.fastjson.JSON;
import com.alibaba.fastjson.TypeReference;
import lombok.NoArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.apache.http.*;
import org.apache.http.client.HttpRequestRetryHandler;
import org.apache.http.client.config.RequestConfig;
import org.apache.http.client.entity.UrlEncodedFormEntity;
import org.apache.http.client.methods.CloseableHttpResponse;
import org.apache.http.client.methods.HttpGet;
import org.apache.http.client.methods.HttpPost;
import org.apache.http.client.methods.HttpRequestBase;
import org.apache.http.client.protocol.HttpClientContext;
import org.apache.http.config.Registry;
import org.apache.http.config.RegistryBuilder;
import org.apache.http.conn.socket.ConnectionSocketFactory;
import org.apache.http.conn.socket.PlainConnectionSocketFactory;
import org.apache.http.conn.ssl.SSLConnectionSocketFactory;
import org.apache.http.conn.ssl.TrustSelfSignedStrategy;
import org.apache.http.entity.ByteArrayEntity;
import org.apache.http.entity.ContentType;
import org.apache.http.impl.client.CloseableHttpClient;
import org.apache.http.impl.client.HttpClients;
import org.apache.http.impl.conn.PoolingHttpClientConnectionManager;
import org.apache.http.message.BasicNameValuePair;
import org.apache.http.pool.PoolStats;
import org.apache.http.protocol.HttpContext;
import org.apache.http.ssl.SSLContextBuilder;
import org.apache.http.util.EntityUtils;
import org.springframework.stereotype.Component;
import org.springframework.util.StringUtils;

import java.io.IOException;
import java.security.KeyManagementException;
import java.security.KeyStoreException;
import java.security.NoSuchAlgorithmException;
import java.util.*;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
import java.util.concurrent.TimeUnit;
import java.util.concurrent.atomic.AtomicInteger;
import java.util.concurrent.locks.Condition;
import java.util.concurrent.locks.ReentrantLock;

/**
 * @author jiangj
 * @version 1.0.0
 * @ClassName HttpHelper.java
 * @Description TODO
 * @createTime 2021年12月28日 16:25:00
 */
@Slf4j
public class HttpHelper {


    public static final String DEFAULT_MAX_TOTAL                  = "2000";
    public static final String DEFAULT_MAX_PER_ROUTE              = "50";
    public static final String DEFAULT_CONNECTION_REQUEST_TIMEOUT = "500";
    public static final String DEFAULT_SOCKET_TIMEOUT             = "4000";
    public static final String DEFAULT_CONNECT_TIMEOUT            = "2000";
    public static final String DEFAULT_RETRY_COUNT                = "3";

    public static final String HTTP_PROTOCOL  = "http";
    public static final String HTTPS_PROTOCOL = "https";

    private static final byte[] LOCK = new byte[0];

    // 0: 正常运行 1:正在关闭 2:已经关闭
    private static final AtomicInteger status      = new AtomicInteger(Status.RUNNING.ordinal());
    private static final ReentrantLock mainLock    = new ReentrantLock();
    private static final Condition termination = mainLock.newCondition();

    private static PoolingHttpClientConnectionManager pool;
    private static RequestConfig requestConfig;
    private static CloseableHttpClient client;

    static {
        try {
            SSLContextBuilder builder = new SSLContextBuilder();
            builder.loadTrustMaterial(null, new TrustSelfSignedStrategy());
            SSLConnectionSocketFactory sslConnectionSocketFactory = new SSLConnectionSocketFactory(builder.build());

            Registry<ConnectionSocketFactory> socketFactoryRegistry = RegistryBuilder.<ConnectionSocketFactory> create()
                    .register("http", PlainConnectionSocketFactory.getSocketFactory())
                    .register("https", sslConnectionSocketFactory)
                    .build();
            pool = new PoolingHttpClientConnectionManager(socketFactoryRegistry);

            // 最大接连数2000
            pool.setMaxTotal(Integer.parseInt(DEFAULT_MAX_TOTAL));

            // 最大路由
            pool.setDefaultMaxPerRoute(Integer.parseInt(DEFAULT_MAX_PER_ROUTE));

            requestConfig = RequestConfig.custom()
                    .setConnectionRequestTimeout(Integer.parseInt(DEFAULT_CONNECTION_REQUEST_TIMEOUT))// 从连接池中获取连接的超时时间
                    .setSocketTimeout(Integer.parseInt(DEFAULT_SOCKET_TIMEOUT))// 连接成功后，请求数据的超时时间
                    .setConnectTimeout(Integer.parseInt(DEFAULT_CONNECT_TIMEOUT))// 连接超时的时间
                    .build();
        } catch (NoSuchAlgorithmException | KeyStoreException | KeyManagementException e) {
            log.error("HttpHelper init failure", e);
        }
    }

    /**
     * 发送json格式内容到指定url
     * @param url 请求地址
     * @param jsonContent 发送内容（json）
     * @return response 请求响应内容
     * @throws ParseException ParseException
     * @throws IOException IOException
     * @author 刘海峰
     * @since 2018/3/9-11:29
     * <p>
     * <strong>示例一：</strong>
     * </p>
     * <blockquote>
     *
     * <pre>
     * {@code
     *      public void post{
     *          String postUrl = "http://172.18.3.122/api/Party/SignOut?name=%E9%99%88%E5%98%89%E6%96%87";
     *          try {
     *          String result = HttpHelper.post(postUrl, "");
     *              ResultModel obj = JSON.parseObject(result, ResultModel.class);
     *              Assert.assertEquals(200, obj.getCode());
     *              Assert.assertTrue(obj.isSuccess());
     *          } catch (IOException e) {
     *              e.printStackTrace();
     *          }
     *      }
     * }
     * </pre>
     *
     * </blockquote>
     */
    public static String post(String url, String jsonContent) throws ParseException, IOException {
        return post(url, jsonContent, null);
    }

    /**
     * 发送json格式内容到指定url
     * @param url 请求地址
     * @param jsonContent 发送内容（json）
     * @param headers 请求头信息
     * @return 请求响应内容
     * @throws ParseException ParseException
     * @throws IOException IOException
     * @author 刘海峰
     * @since 2018/3/9-11:29
     */
    public static String post(String url, String jsonContent, Map<String, String> headers) throws ParseException, IOException {
        HttpEntity requestEntity = new ByteArrayEntity(jsonContent.getBytes());
        return post(url, requestEntity, headers, null);
    }

    /**
     * 发送json格式内容到指定url
     * @param url 请求地址
     * @param jsonContent 发送内容（json）
     * @param headers 请求头信息
     * @param socketTimeout 请求超时时间，单位ms
     * @return 请求响应内容
     * @throws ParseException ParseException
     * @throws IOException IOException
     * @author yuhuan
     * @since 2018/8/8
     */
    public static String post(String url, String jsonContent, Map<String, String> headers, int socketTimeout) throws ParseException, IOException {
        return post(url, new ByteArrayEntity(jsonContent.getBytes()), headers, null, getRequestConfig(socketTimeout));
    }

    /**
     * 发送内容到指定url
     * @param url 请求地址
     * @param requestEntity 请求内容
     * @param headers 请求头信息
     * @param contentType 内容类型
     * @return 请求响应内容
     * @throws ParseException ParseException
     * @throws IOException IOException
     * @author 刘海峰
     * @since 2018/3/9-11:29
     */
    public static String post(String url, HttpEntity requestEntity, Map<String, String> headers, ContentType contentType) throws ParseException, IOException {
        return post(url, requestEntity, headers, contentType, null);
    }

    /**
     * 发送内容到指定url
     * @param url 请求地址
     * @param requestEntity 请求内容
     * @param headers 请求头信息
     * @param contentType 内容类型
     * @param socketTimeout 请求超时时间，单位ms
     * @return 请求响应内容
     * @throws ParseException ParseException
     * @throws IOException IOException
     * @author 刘海峰
     * @since 2018/3/9-11:29
     */
    public static String post(String url, HttpEntity requestEntity, Map<String, String> headers, ContentType contentType,
                              int socketTimeout) throws ParseException, IOException {
        return post(url, requestEntity, headers, contentType, getRequestConfig(socketTimeout));
    }

    /**
     * 发送form格式内容
     * @param url 请求地址
     * @param map 键值对
     * @param headers 请求头信息
     * @return 请求响应内容
     * @throws IOException IOException
     * @author 刘海峰
     * @since 2018/3/9-11:29
     * <p>
     * <strong>示例一：</strong>
     * </p>
     * <blockquote>
     *
     * <pre>
     * {@code
     *      public void post{
     *          try {
     *              Map map = ObjectHelper.getParameter("name", "陈嘉文");
     *              String result = HttpHelper.post("http://172.18.3.122/api/Party/Message", map, null);
     *              ResultModel obj = JSON.parseObject(result, ResultModel.class);
     *              Assert.assertEquals(200, obj.getCode());
     *              Assert.assertTrue(obj.isSuccess());
     *          } catch (IOException e) {
     *              e.printStackTrace();
     *          }
     *      }
     * }
     * </pre>
     *
     * </blockquote>
     */
    public static String post(String url, Map<String, String> map, Map<String, String> headers) throws IOException {
        List<NameValuePair> nameValuePairs = new ArrayList<>();
        if (map != null) {
            for (Map.Entry<String, String> entry : map.entrySet()) {
                nameValuePairs.add(new BasicNameValuePair(entry.getKey(), entry.getValue()));
            }
        }
        HttpEntity requestEntity = new UrlEncodedFormEntity(nameValuePairs, Consts.UTF_8);
        return post(url, requestEntity, headers, ContentType.APPLICATION_FORM_URLENCODED);
    }

    /**
     * 发送get请求，返加指定url的内容
     * @param url 请求路径
     * @return 请求响应内容
     * @throws ParseException ParseException
     * @throws IOException IOException
     * @author 刘海峰
     * @since 2018/3/9-11:29
     * <p>
     * <strong>示例一：</strong>
     * </p>
     * <blockquote>
     *
     * <pre>
     * {@code
     *      public void get{
     *          String result = HttpHelper.get("http://www.baidu.com/");
     *      }
     * }
     * </pre>
     *
     * </blockquote>
     */
    public static String get(String url) throws ParseException, IOException {
        return get(url, null);
    }

    /**
     * 发送get请求，返加指定url的内容
     * @param url 请求路径
     * @param headers 请求头
     * @return 请求响应内容
     * @throws ParseException ParseException
     * @throws IOException IOException
     * @author yuhuan
     * @since 2018/8/4
     * <p>
     * <strong>示例一：</strong>
     * </p>
     * <blockquote>
     *
     * <pre>
     * {@code
     *      public void get{
     *          Map<String, String> headers = new HashMap<>();
     *          headers.put("name", "value");
     *          String result = HttpHelper.get("http://www.baidu.com/", headers);
     *      }
     * }
     * </pre>
     *
     * </blockquote>
     */
    public static String get(String url, Map<String, String> headers) throws ParseException, IOException {
        return get(url, headers, null);
    }

    public static String get(String url, Map<String, String> headers, int socketTimeout) throws ParseException, IOException {
        return get(url, headers, getRequestConfig(socketTimeout));
    }

    public static String get(String url, Map<String, String> headers, RequestConfig requestConfig) throws ParseException, IOException {
        HttpGet httpGet = new HttpGet(url);
        if (headers != null && headers.size() > 0) {
            for (Map.Entry<String, String> entry : headers.entrySet()) {
                httpGet.setHeader(entry.getKey(), entry.getValue());
            }
        }
        return execute(createClient(), httpGet, requestConfig);
    }

    public static String get(CloseableHttpClient client, String url, Map<String, String> headers) throws ParseException, IOException {
        HttpGet httpGet = new HttpGet(url);
        if (headers != null && headers.size() > 0) {
            for (Map.Entry<String, String> entry : headers.entrySet()) {
                httpGet.setHeader(entry.getKey(), entry.getValue());
            }
        }
        return execute(client, httpGet);
    }

    /**
     * 发送内容到指定url
     * @param url 请求地址
     * @param requestEntity 请求内容
     * @param headers 请求头信息
     * @param contentType 内容类型
     * @param requestConfig 请求参数设置
     * @return 请求响应内容
     * @throws ParseException ParseException
     * @throws IOException IOException
     */
    public static String post(String url, HttpEntity requestEntity, Map<String, String> headers, ContentType contentType,
                              RequestConfig requestConfig) throws ParseException, IOException {
        HttpPost httpPost = new HttpPost(url);
        if (headers != null && headers.size() > 0) {
            for (Map.Entry<String, String> entry : headers.entrySet()) {
                httpPost.setHeader(entry.getKey(), entry.getValue());
            }
        }
        if (contentType == null) {
            contentType = ContentType.APPLICATION_JSON;
        }
        httpPost.setHeader(HttpHeaders.CONTENT_TYPE, contentType.getMimeType());
        if (requestEntity != null) {
            httpPost.setEntity(requestEntity);
        }
        return execute(createClient(), httpPost, requestConfig);
    }

    /**
     * 发送内容到指定url
     * @param url 请求地址
     * @param requestEntity 请求内容
     * @param headers 请求头信息
     * @param contentType 内容类型
     * @return 请求响应内容
     * @throws ParseException ParseException
     * @throws IOException IOException
     * @author 刘海峰
     * @since 2018/3/9-11:29
     */
    public static String post(CloseableHttpClient client, String url, HttpEntity requestEntity, Map<String, String> headers,
                              ContentType contentType) throws ParseException, IOException {
        HttpPost httpPost = new HttpPost(url);
        if (headers != null && headers.size() > 0) {
            for (Map.Entry<String, String> entry : headers.entrySet()) {
                httpPost.setHeader(entry.getKey(), entry.getValue());
            }
        }
        if (contentType == null) {
            contentType = ContentType.APPLICATION_JSON;
        }
        httpPost.setHeader(HttpHeaders.CONTENT_TYPE, contentType.getMimeType());
        if (requestEntity != null) {
            httpPost.setEntity(requestEntity);
        }
        return execute(client, httpPost);
    }

    public static String execute(CloseableHttpClient client, HttpRequestBase request, RequestConfig requestConfig) throws IOException {
        request.setConfig(requestConfig == null ? HttpHelper.requestConfig : requestConfig);
        return execute(client, request);
    }

    public static String execute(CloseableHttpClient client, HttpRequestBase request) throws IOException {
        try (CloseableHttpResponse response = client.execute(request)) {
            String body = null;
            int statusCode = response.getStatusLine().getStatusCode();
            if (statusCode == HttpStatus.SC_OK) {
                HttpEntity entity = response.getEntity();
                if (entity != null) {
                    body = EntityUtils.toString(entity, Consts.UTF_8);
                }
                EntityUtils.consume(entity);
            } else {
                log.error("request url error:{},status code:{}", request.getURI().toString(), statusCode);
            }
            request.releaseConnection();
            return body;
        }
    }

    /**
     * url判断是否为http
     * @param url url
     */
    public static boolean isHttp(String url) {
        if (StringUtils.isEmpty(url)) return false;
        return url.startsWith(HTTP_PROTOCOL);
    }

    /**
     * url判断是否为https
     * @param url url
     */
    public static boolean isHttps(String url) {
        if (StringUtils.isEmpty(url)) return false;
        return url.startsWith(HTTPS_PROTOCOL);
    }

    /**
     * 拼接url
     * @param str 字符串数组
     */
    public static String buildUrl(String... str) {
        StringBuilder builder = new StringBuilder();
        for (String s : str) {
            if (s == null) {
                continue;
            }
            builder.append(s);
        }
        return builder.toString();
    }

    public static CloseableHttpClient createClient() {
        if (status.get() != Status.RUNNING.ordinal()) {
            throw new RuntimeException("HttpClient has been shutdown");
        }

        if (client == null) {
            synchronized (LOCK) {
                if (client == null) {
                    client = HttpClients.custom()
                            .setConnectionManager(pool)
                            .setDefaultRequestConfig(requestConfig)
                            .setConnectionManagerShared(true)
                            .setRetryHandler(RetryHandler.INSTANCE)
                            .build();
                }
            }
        }
        return client;
    }

    public static CloseableHttpClient createClient(int socketTimeout) {
        if (status.get() != Status.RUNNING.ordinal()) {
            throw new RuntimeException("HttpClient has been shutdown");
        }

        if (socketTimeout > 0) {
            RequestConfig requestConfig = RequestConfig.custom()
                    .setConnectionRequestTimeout(Integer
                            .parseInt(DEFAULT_CONNECTION_REQUEST_TIMEOUT))// 从连接池中获取连接的超时时间
                    .setSocketTimeout(socketTimeout)// 连接成功后，请求数据的超时时间
                    .setConnectTimeout(Integer.parseInt(DEFAULT_CONNECT_TIMEOUT))// 连接超时的时间
                    .build();
            return HttpClients.custom()
                    .setConnectionManager(pool)
                    .setDefaultRequestConfig(requestConfig)
                    .setConnectionManagerShared(true)
                    .setRetryHandler(RetryHandler.INSTANCE)
                    .build();
        } else {
            return createClient();
        }
    }

    public static RequestConfig getRequestConfig(int socketTimeout) {
        if (socketTimeout == 0) {
            return requestConfig;
        }
        return RequestConfig.custom()
                .setConnectionRequestTimeout(
                        Integer.parseInt(DEFAULT_CONNECTION_REQUEST_TIMEOUT))// 从连接池中获取连接的超时时间
                .setSocketTimeout(socketTimeout)// 连接成功后，请求数据的超时时间
                .setConnectTimeout(Integer.parseInt(DEFAULT_CONNECT_TIMEOUT))// 连接超时的时间
                .build();
    }

    private static void shutdown() {
        status.set(Status.CLOSING.ordinal());
        ExecutorService singleThreadExecutor = Executors.newSingleThreadExecutor();
        singleThreadExecutor.execute(() -> {
            try {
                while (!tryShutdown()) {
                    Thread.sleep(10);
                }
            } catch (InterruptedException e) {
                Thread.currentThread().interrupt();
            }
        });
    }

    private static boolean tryShutdown() {
        mainLock.lock();
        try {
            if (pool == null) {
                log.debug("Set HttpHelper Status CLOSED");
                status.set(Status.CLOSED.ordinal());
                termination.signalAll();
                return true;
            }

            if (isShutdown()) {
                pool.shutdown();
                pool = null;
                log.debug("Set HttpHelper Status CLOSED");
                status.set(Status.CLOSED.ordinal());
                termination.signalAll();
                return true;
            }
        } finally {
            mainLock.unlock();
        }
        return false;
    }

    private static void shutdownNow() {
        mainLock.lock();
        try {
            if (pool != null) {
                pool.shutdown();
                pool = null;
            }
        } finally {
            mainLock.unlock();
        }
    }

    private static boolean isShutdown() {
        if (pool == null) {
            return true;
        }
        int leased = pool.getTotalStats().getLeased();
        int pending = pool.getTotalStats().getPending();
        return leased == 0 && pending == 0;
    }

    public static PoolStats getPoolStats() {
        if (pool != null) {
            return pool.getTotalStats();
        }
        return null;
    }

    private static boolean awaitTermination(long timeout, TimeUnit unit) throws InterruptedException {
        mainLock.lock();
        long nanos = unit.toNanos(timeout);
        try {
            for (;;) {
                if (status.get() == Status.CLOSED.ordinal()) {
                    log.debug("awaitTermination get HttpHelper Status CLOSED, return");
                    return true;
                }
                if (nanos <= 0) return false;
                nanos = termination.awaitNanos(nanos);
            }
        } finally {
            mainLock.unlock();
        }
    }

    enum Status {
        RUNNING,
        CLOSING,
        CLOSED,;
    }

    /**
     * 重试处理，默认只对NoHttpResponseException重试3次
     */
    @NoArgsConstructor
    static class RetryHandler implements HttpRequestRetryHandler {

        public static final RetryHandler INSTANCE = new RetryHandler();

        private int retryCount = Integer.parseInt(DEFAULT_RETRY_COUNT);

        public void setRetryCount(int retryCount) {
            this.retryCount = retryCount;
        }

        @Override
        public boolean retryRequest(IOException exception, int executionCount, HttpContext context) {
            if (exception instanceof NoHttpResponseException) {
                if (executionCount >= retryCount) {
                    log.error("maximum [{} times] tries reached, NoHttpResponseException would be thrown to outer block", retryCount);
                    return false;
                }
                String httpRoute = null;
                if (context != null && context.getAttribute(HttpClientContext.HTTP_ROUTE) != null) {
                    httpRoute = context.getAttribute(HttpClientContext.HTTP_ROUTE).toString();
                }
                log.warn("NoHttpResponseException from server [{}] on " + executionCount + " call", httpRoute);
                return true;
            }
            return false;
        }
    }

    @Slf4j
    public static class ShutdownHook {

        private static final ShutdownHook shutdownHook = new ShutdownHook();

        public static ShutdownHook getShutdownHook() {
            return shutdownHook;
        }

        public void doDestroy() {
            log.debug("HttpClient Run shutdown hook now.");
            if (status.compareAndSet(Status.RUNNING.ordinal(), Status.CLOSING.ordinal())) {
                mainLock.lock();
                try {
                    int waitTime = 10000;
                    //int waitTime = Integer.parseInt(StringUtils.defaultIfBlank(SwjConfig.get(Constant.SHUTDOWN_WAIT), Constant.DEFAULT_SHUTDOWN_WAIT));
                    shutdown();
                    if (!awaitTermination(waitTime, TimeUnit.MILLISECONDS)) {
                        log.warn("HttpClient did not shut down gracefully within " + waitTime + " milliseconds. Proceeding with forceful shutdown");
                        shutdownNow();
                    }
                } catch (InterruptedException e) {
                    shutdownNow();
                    Thread.currentThread().interrupt();
                } finally {
                    mainLock.unlock();
                    log.debug("HttpClient Run shutdown hook end.");
                }
            }
        }
    }



}
